// util.hpp : 工具模块，存放其他模块可能使用到的公共方法
#pragma once

#include <iostream>
#include <string>                     //std::string \ to_string()
#include <vector>                     //std::vector
#include <atomic>                     //std::atomic_uint : C++11里增加的原子性计数器
#include <fstream>                    //std::ofstream \ std::ifstream
#include <sys/types.h>                //stat(路径，输出型参数)
#include <sys/stat.h>                 //stat()
#include <unistd.h>                   //stat()
#include <ctime>                      //time() \ localtime() \ strftime()
#include <sys/time.h>                 //gettimeofday()
#include <boost/algorithm/string.hpp> //boost::split() : 切割字符串
#include <random>
#include <regex> //进行正则匹配

#include <cstring>

#include <openssl/bio.h> //进行base64编码
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <openssl/md5.h>                                   //进行md5散列
#include <openssl/aes.h>                                   //对称加密
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // 消除md5-Wdeprecated-declarations警告

#include <jwt-cpp/base.h> //引入jwt进行token验证
#include <jwt-cpp/jwt.h>
#include <jwt-cpp/picojson.h>
#include <exception>

namespace ns_util
{

    const std::string temp_path = "./temp/"; // 路径（放在外面是为了便于修改）

    /*路径生成工具类：生产路径+后缀的完整文件名*/
    class PathUtil
    {
    public:
        /**********************************************************
         * 给文件名添加路径和后缀（使用C++字符串的拼接）
         *  构建源文件路径+后缀的完整文件名：1234 -> ./temp/1234.cpp
         》将对外提供的方法设置为static，这样不创建对象也能调用
         **********************************************************/
        static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
        {
            std::string path_name = temp_path; // 添加路径
            path_name += file_name;
            path_name += suffix; // 添加后缀
            return path_name;
        }

        // 编译时需要的文件的文件名
        /*构建 要编译文件 的完整路径+后缀名*/
        static std::string Src(const std::string &file_name)
        {
            return AddSuffix(file_name, ".cpp");
        }
        /*构建 可执行程序 的完整路径+后缀名*/
        static std::string Exe(const std::string &file_name)
        {
            return AddSuffix(file_name, ".exe");
        }
        /*构建 编译错误文件 的完整路径+后缀名*/
        static std::string CompilerError(const std::string &file_name)
        {
            return AddSuffix(file_name, ".compile_error");
        }

        // 运行时需要的临时文件的文件名
        /*构建 程序标准输入 的完整路径+后缀名*/
        static std::string Stdin(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdin");
        }
        /*构建 程序标准输出 的完整路径+后缀名*/
        static std::string Stdout(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdout");
        }
        /*构建 程序标准错误 的完整路径+后缀名*/
        static std::string Stderr(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stderr");
        }
    };

    /*时间类：有关时间获取的操作*/
    class TimeUtil
    {
    public:
        /*获取标准时间:年月日时分秒*/
        static std::string GetTime()
        {
            // 使用C库函数time()来获取时间戳
            time_t timestamp = time(nullptr); // 获取时间戳
            struct tm *timeinfo;              // 保存时间信息的结构体
            char tmbuffer[128] = {0};         // 保存数据信息的字符串
            timeinfo = localtime(&timestamp); // 用时间戳计算出时间结构体的参数
            strftime(tmbuffer, sizeof(tmbuffer), "%Y/%m/%d %H:%M:%S", timeinfo);

            // char* 可以直接转为string.
            return tmbuffer;
        }

        /*获取秒时间戳*/
        static std::string GetTimeStamp()
        {
            // 使用系统调用接口gettimeofda()来获取时间戳
            struct timeval _time;
            gettimeofday(&_time, nullptr);
            //_time.tv_sec : 累计到现在的秒数。
            return std::to_string(_time.tv_sec);
        }
        /*获取毫秒时间戳*/
        static std::string GetTimeMs()
        {
            // 使用系统调用接口gettimeofda()来获取时间戳
            struct timeval _time;
            gettimeofday(&_time, nullptr);
            //_time.tv_sec : 累计到现在的秒数。
            //_time.tv_usec : 累计到现在的微秒数。
            return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
        }
    };

    /*文件工具类：有关文件的操作*/
    class FileUtil
    {
    public:
        /**************************************************************
         * 判断文件是否存在
         *  传入一个文件的路径，通过系统调用接口stat()来判断文件是否存在
         *  也可以系统调用open()通过读方式打开文件来判断，打开失败表示文件不存在。
         》返回布尔值的方法以 is 或 are 开头（命名函数的技巧）
         **************************************************************/
        static bool IsFileExists(const std::string &path_name)
        {
            /*****************************************************************
             * stat(路径，输出型参数用来接收文件属性) :
             * 用来获取文件属性，获取成功返回0，失败返回-1，获取成功表示文件存在。
             *****************************************************************/
            struct stat st;
            if (stat(path_name.c_str(), &st) == 0)
            {
                // 获取属性成功，文件已经存在
                return true;
            }

            return false;
        }

        /**************************************************************************
         * 生成一个具有唯一性的文件名，用 毫秒级时间戳+原子性递增的静态变量 来保证唯一性。
         * uniq是英文unique(唯一)的缩写
         **************************************************************************/
        static std::string UniqFileName()
        {
            // 获取定义在静态区，具有原子性的计数器
            static std::atomic_uint id(0); // atomic : C++11里增加的原子性计数器
            id++;
            // 获取毫秒级时间戳
            std::string ms = TimeUtil::GetTimeMs();
            // 毫秒级时间戳+原子性递增的静态变量保证唯一性。
            std::string uniq_id = std::to_string(id); // to_string:将数字转化为字符串
            return ms + "_" + uniq_id;
        }

        /****************************
         * 往文件中写入内容
         * 参数：
         *     target  : 目标文件名
         *     content : 要写入的内容
         ****************************/
        static bool WriteFile(const std::string &target, const std::string &content)
        {
            std::ofstream out(target); // 以写方式打开文件
            if (!out.is_open())        // 文件如果打开失败
            {
                return false;
            }
            out.write(content.c_str(), content.size()); // 向文件中写入内容，参数:(要写的内容，内容的大小)
            out.close();                                // 关闭文件
            return true;
        }
        /*********************************************
         * 读取文件内容
         * 参数：
         *   target  : 目标文件名
         *   content : 输出型参数，从文件中读到的内容
         *   keep    : 是否保留文件中的换行符"\n"
         *********************************************/
        static bool ReadFile(const std::string &target, std::string *content, bool keep = false)
        {
            (*content).clear(); // 清空输出型参数

            std::ifstream in(target); // 以读方式打开文件
            if (!in.is_open())        // 文件如果打开失败
            {
                return false;
            }
            std::string line;
            // getline:不保存行分割符,有些时候需要保留\n.
            while (std::getline(in, line)) // 按行从文件中读取内容
            {
                (*content) += line;               // 向输出型参数里写入文件内容
                (*content) += (keep ? "\n" : ""); // 保留换行符
                // line.clear();
            }
            in.close(); // 关闭文件
            return true;
        }
    };

    /*字符串工具类*/
    class StringUtil
    {
    public:
        /****************************************
         * 描述：切分字符串
         * 参数：
         *     str    : 要切分的字符串
         *     target : 输出型，保存切分完毕的结果
         *     sep    : 指定的分割符
         * **************************************/

        static void SplitString(const std::string &str, std::vector<std::string> *target, const std::string &sep)
        {
            // boost split
            boost::split((*target), str, boost::is_any_of(sep), boost::algorithm::token_compress_on);
        }

        static bool IsEmail(std::string email)
        {
            std::regex pattern("([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)");
            if (std::regex_match(email, pattern))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    };

    class Openssl
    {
    public:
    public:
        static int Base64Code(const std::string str, std::string &base64Code)
        {
            BIO *bmem = NULL;
            BIO *b64 = NULL;
            BUF_MEM *bptr = NULL;

            b64 = BIO_new(BIO_f_base64());
            BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
            bmem = BIO_new(BIO_s_mem());
            b64 = BIO_push(b64, bmem);
            BIO_write(b64, str.c_str(), str.length());
            BIO_flush(b64);
            BIO_get_mem_ptr(b64, &bptr);

            base64Code = std::string(bptr->data, bptr->length);
            BIO_free_all(b64);
            return 0;
        }

        static int Base64Decode(std::string base64Code, std::string &decode)
        {
            BIO *b64 = NULL;
            BIO *bmem = NULL;

            b64 = BIO_new(BIO_f_base64());
            BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
            bmem = BIO_new_mem_buf(base64Code.c_str(), base64Code.length());
            bmem = BIO_push(b64, bmem);

            char buf[1024] = {0};
            BIO_read(bmem, buf, 1024);
            decode = std::string(buf, strlen(buf));
            BIO_free_all(bmem);
            return 0;
        }

        static std::string getHashMd5ToHex(std::string str)
        {
            unsigned char md[MD5_DIGEST_LENGTH] = {0};
            if (MD5((unsigned char *)str.c_str(), str.size(), md) == NULL)
            {
                return "";
            }

            std::string md5_hex;
            const char map[] = "0123456789abcdef";
            for (size_t i = 0; i < MD5_DIGEST_LENGTH; i++)
            {
                md5_hex += map[md[i] / 16];
                md5_hex += map[md[i] % 16];
            }
            return md5_hex;
        }
        static void AES_en(std::string message, std::string &enc_message)
        {
            const char *key = "1234567887654321";
            int len = message.length() + 1;
            int length = len % 16 != 0 ? ((len / 16) + 1) * 16 : len;
            unsigned char *out = new unsigned char[length];
            unsigned char ivec[AES_BLOCK_SIZE];
            memset(ivec, 0, sizeof(ivec));

            AES_KEY encKey;
            AES_set_encrypt_key((const unsigned char *)key, 128, &encKey);

            AES_cbc_encrypt((const unsigned char *)message.c_str(), out, length, &encKey, ivec, AES_ENCRYPT);
            Base64Code(std::string((char *)out, length), enc_message);
            delete[] out;
        }
        static void AES_de(std::string enc_message, std::string &dec_message)
        {
            std::string debase64;
            Base64Decode(enc_message, debase64);

            const char *key = "1234567887654321";
            int length = debase64.length();
            unsigned char *data = new unsigned char[length + 1];
            memset(data, 0, length * sizeof(char));
            unsigned char ivec[AES_BLOCK_SIZE];
            memset(ivec, 0, sizeof(ivec));

            AES_KEY deckey;
            AES_set_decrypt_key((const unsigned char *)key, 128, &deckey);
            AES_cbc_encrypt((const unsigned char *)debase64.c_str(), data, length, &deckey, ivec, AES_DECRYPT);

            dec_message = std::string((char *)data, strlen((char *)data));
            delete[] data;
        }
    };

    class TokenVerify
    {
    public:
        static bool verify(std::string token, std::string &username, std::string &role)
        {
            auto decoded = jwt::decode(token);

            std::string userid;
            std::string now;
            std::string outtime;
            for (auto &e1 : decoded.get_payload_json())
            {
                if (e1.first == "userid")
                    userid = e1.second.to_str();
                else if (e1.first == "outtime")
                    outtime = e1.second.to_str();
                else if (e1.first == "username")
                    username = e1.second.to_str();
                else if (e1.first == "role")
                    role = e1.second.to_str();
            }

            // token过期无效
            if (atol(outtime.c_str()) < atol(std::to_string(time(NULL)).c_str()))
            {
                std::cout << "token -> 验证失败 token超时" << std::endl;
                return false;
            }

            std::string sgin_str = Openssl::getHashMd5ToHex(userid);

            auto verifier = jwt::verify().allow_algorithm(jwt::algorithm::hs256{sgin_str}).with_issuer("auth0");
            try
            {
                verifier.verify(decoded);
                std::cout << "token -> 验证成功" << std::endl;
                return true;
            }
            catch (const std::exception &e)
            {
                std::cout << "token -> 验证失败" << std::endl;
                return false;
            }
        }

        static bool verify(std::string token, std::string user, std::string &username, std::string &role)
        {
            auto decoded = jwt::decode(token);

            std::string userid;
            std::string now;
            std::string outtime;
            for (auto &e1 : decoded.get_payload_json())
            {
                if (e1.first == "userid")
                    userid = e1.second.to_str();
                else if (e1.first == "outtime")
                    outtime = e1.second.to_str();
                else if (e1.first == "username")
                    username = e1.second.to_str();
                else if (e1.first == "role")
                    role = e1.second.to_str();
            }

            // token过期无效
            if (atol(outtime.c_str()) < atol(std::to_string(time(NULL)).c_str()) || user != username)
            {
                std::cout << "token -> 验证失败" << std::endl;
                return false;
            }

            std::string sgin_str = Openssl::getHashMd5ToHex(userid);

            auto verifier = jwt::verify().allow_algorithm(jwt::algorithm::hs256{sgin_str}).with_issuer("auth0");
            try
            {
                verifier.verify(decoded);
                std::cout << "token -> 验证成功" << std::endl;
                return true;
            }
            catch (const std::exception &e)
            {
                std::cout << "token -> 验证失败" << std::endl;
                return false;
            }
        }

        static bool verify(std::string token, int &out_userid, std::string &role)
        {
            auto decoded = jwt::decode(token);

            std::string userid;
            std::string now;
            std::string outtime;
            for (auto &e1 : decoded.get_payload_json())
            {
                if (e1.first == "userid")
                    userid = e1.second.to_str();
                else if (e1.first == "outtime")
                    outtime = e1.second.to_str();
                else if (e1.first == "role")
                    role = e1.second.to_str();
            }

            // token过期无效
            if (atol(outtime.c_str()) < atol(std::to_string(time(NULL)).c_str()))
            {
                std::cout << "token -> 验证失败 token超时" << std::endl;
                return false;
            }

            out_userid = atoi(userid.c_str());
            std::string sgin_str = Openssl::getHashMd5ToHex(userid);

            auto verifier = jwt::verify().allow_algorithm(jwt::algorithm::hs256{sgin_str}).with_issuer("auth0");
            try
            {
                verifier.verify(decoded);
                std::cout << "token -> 验证成功" << std::endl;
                return true;
            }
            catch (const std::exception &e)
            {
                std::cout << "token -> 验证失败" << std::endl;
                return false;
            }
        }
    };

    class RandomUtil
    {
    public:
        static int random_6()
        {
            std::string res = "";
            std::default_random_engine e;
            std::uniform_int_distribution<int> u(0, 9);
            e.seed(time(NULL));

            for (int i = 0; i < 6; i++)
            {
                int r = u(e);
                if (r == 0 && i == 0)
                    r += 1;
                res += ('0' + r);
            }
            return atoi(res.c_str());
        }
    };

}